/**
 * 
 */
package app.dev.telemetric;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Random;

/**
 * @author David
 *
 */
public class Client extends Thread {
	private final int SLEEP_MIN=100;
	private final int SLEEP_MAX=1000;
	private final int MAX_STREAMS=360;
	private final int MIN_STREAMS=1;
	private final float STREAMS_FACTOR=((float)(MAX_STREAMS-MIN_STREAMS))/(float)MAX_STREAMS;
	private final double SLEEP_FACTOR=0.9;
	private final int LEN_ID=4;
	private final String USER="usuario1";
	private final Integer APPLICATION=1;
	private final int ID_BASE=6;
	private final short RPM_MAX=6000;
	private final short VELOCIDAD_MAX=205;
	private final short TEMPERATURA_MAX=150;
	private final int TIEMPO_BASE=1137421;
	
	private static Random rand=new Random();
	private int t_sleep;
	private int id;
	private int protocol;
	private short rpm;
	private short velocidad;
	private short temperatura;
	private int tiempo;
	private long stream_generates;
	private Socket socket;
	private InputStream dataIn;
	private OutputStream dataOut;
	private byte[] stream;
	private ArrayList<byte[]> lost_streams;
	private int len;
	public long sends;
	public long stimated_time;
	private long stream_id=0;
	
	public boolean finalized=false;
	public boolean fail=false;
	
	private byte[] ToByte(byte value)
	{
		byte[] ret=new byte[1];
		int i;
		
		for(i=0; i<ret.length; i++)
			ret[(ret.length-1)-i]=(byte)(value);
		
		return ret;
	}
	
	private byte[] ToByte(short value)
	{
		byte[] ret=new byte[2];
		int i;
		
		for(i=0; i<ret.length; i++)
			ret[(ret.length-1)-i]=(byte)(value>>(i*8));
		
		return ret;
	}
	
	private byte[] ToByte(int value)
	{
		byte[] ret=new byte[4];
		int i;
		
		for(i=0; i<ret.length; i++)
			ret[(ret.length-1)-i]=(byte)(value>>(i*8));
		
		return ret;
	}
	/**
	 * 
	 */
	public Client() {
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param target
	 */
	public Client(Runnable target) {
		super(target);
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param name
	 */
	public Client(String name) {
		super(name);
		
		Integer i=new Integer(name);
		
		socket=null;
		id=i+ID_BASE;
		t_sleep=(int)(((float) rand.nextInt(SLEEP_MAX)*SLEEP_FACTOR)+ (float)SLEEP_MIN);
		stream_generates=(int)(((float) rand.nextInt(MAX_STREAMS)*STREAMS_FACTOR)+(float)MIN_STREAMS);
		protocol=(id%2)+1;
		tiempo=TIEMPO_BASE;
		sends=stream_generates;
		stimated_time=sends*t_sleep;
		lost_streams=new ArrayList<byte[]>();
		
		System.out.print("Device: "+id);
		System.out.print(", Elapsed time: "+t_sleep);
		System.out.print(", Streams: "+stream_generates);
		System.out.println(", Protocol: "+protocol);
	}

	/**
	 * @param group
	 * @param target
	 */
	public Client(ThreadGroup group, Runnable target) {
		super(group, target);
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param group
	 * @param name
	 */
	public Client(ThreadGroup group, String name) {
		super(group, name);
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param target
	 * @param name
	 */
	public Client(Runnable target, String name) {
		super(target, name);
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param group
	 * @param target
	 * @param name
	 */
	public Client(ThreadGroup group, Runnable target, String name) {
		super(group, target, name);
		// TODO Auto-generated constructor stub
	}

	/**
	 * @param group
	 * @param target
	 * @param name
	 * @param stackSize
	 */
	public Client(ThreadGroup group, Runnable target, String name,
			long stackSize) {
		super(group, target, name, stackSize);
		// TODO Auto-generated constructor stub
	}

	public void run()
	{
		boolean next = false;
		
		try{
			
			while(socket==null)
			{
				sleep(t_sleep);
				try{
					socket=new Socket("localhost", 7500);
				}catch(ConnectException excep)
				{
					socket=null;
				}
			}
		
			dataOut = socket.getOutputStream();
			dataOut.flush();  
		
			dataIn = socket.getInputStream();
			
			while(!next)
			{
				stream=HelloStream();
				dataOut.write(stream);
				dataOut.flush();
	        
				len = dataIn.read(stream);
				if(stream[0]==1)
					next=true;
			}
			
			next=false;
			while(!next)
			{
				stream=IdStream();
				dataOut.write(stream);
				dataOut.flush();
	        
				len = dataIn.read(stream);
				if(stream[0]==1)
					next=true;
			}
			
			next=false;
			while(!next)
			{
				stream=ProtocolStream();
				dataOut.write(stream);
				dataOut.flush();
	        
				len = dataIn.read(stream);
				if(stream[0]==1)
					next=true;
			}
			
		
			next=false;
			while(!next)
			{
				stream=BeginStream();
				dataOut.write(stream);
				dataOut.flush();
	        
				len = dataIn.read(stream);
				if(stream[0]==1)
					next=true;
			}
			
			try{
				if(protocol==1)
					EnviaDatos1();
				else
					EnviaDatos2();
				
			}catch(Exception e)
			{
				System.err.println("ERROR:: Fin de telemetria de Dispositivo "+this.id);
				e.printStackTrace();
				fail=true;
			}
			
			stream=EndStream();
			dataOut.write(stream);
			dataOut.flush();
			
			next=false;
			stream=new byte[5];
			while(!next)
			{
				len = dataIn.read(stream);
				if((len==1) && stream[len-1]==1)
				{
					//System.out.println("Cliente: "+id+" --- Longitud ack: "+len+" Id stream: "+stream[len-1]);
					next=true;
				}
			}
			//System.out.println("Cliente "+id+" recibio ack");
			
			while(lost_streams.size()>0)
			{
				stream=lost_streams.get(0);
				lost_streams.remove(0);
				//System.out.println("Trama reenviada: "+ByteBuffer.wrap(stream,1, 4).getInt());
				dataOut.write(stream);
				dataOut.flush();
			}
			
			finalized=true;
		}catch(Exception e)
		{
			System.err.println("ERROR:: Dispositivo "+this.id);
			e.printStackTrace();
			fail=true;
		}
		
		try {
			socket.shutdownInput();
			socket.shutdownOutput();
			socket.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("Fin cliente "+id);
	}

	private byte[] EndStream() 
	{
		byte[] ret=new byte[1];
		
		ret[0]=6;
		
		return ret;
	}

	private void EnviaDatos2() throws IOException, InterruptedException 
	{
		byte[] newstream;
		
		while(stream_generates>0)
		{
			//System.out.println(stream_generates);
			newstream=GenerateDataStreamP21();
			if(stream_id>1)
				EsperarACK();
			stream=newstream;
			dataOut.write(stream);
			dataOut.flush();
			
			stream_generates--;
		}
		stream_id++;
		EsperarACK();
	}

	private void EsperarACK() {
		// TODO Auto-generated method stub
		int len;
		long id=-1;
		byte[] buff=new byte[5];
		byte[] aux=null;
		
		try {
			if(dataIn.available()>0)
			{
				len=dataIn.read(buff);
				//System.out.println("long: "+ len+" === ack: ["+buff[0]+" "+buff[1]+" "+buff[2]+" "+buff[3]+" "+buff[4]+"]");
				if(len==5)
					id=0xffffffffl & ByteBuffer.wrap(buff,1,4).getInt();
				
				if(id!=stream_id-2)
					lost_streams.add(stream);
				else
				{
					int i=0;
					
					while(i<lost_streams.size() && aux==null)
					{
						aux=lost_streams.get(i);
						i++;
					}
					
					if(i<lost_streams.size())
						lost_streams.remove(i);
				}
			}
			else
				lost_streams.add(stream);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			lost_streams.add(stream);
		}
			
	}

	private byte[] GenerateDataStreamP21() 
	{
		byte[] ret=new byte[1+LEN_ID+1+10+((Short.SIZE/8)+Integer.SIZE/8)*5+((Short.SIZE/8)*2+(Integer.SIZE/8))*5];
		byte[] lens;
		ByteBuffer bf;
		int i, j=1, k;
		
		ret[0]=21;
		bf=ByteBuffer.wrap(ret);
		lens=ToByte((int)stream_id);
		for(i=0; i<lens.length; i++)
			bf.put(j+i, lens[i]);
		stream_id++;
		
		lens=ToByte((byte)10);
		j+=i;
		for(i=0; i<lens.length; i++)
			bf.put(j+i, lens[i]);
		for(k=0; k<5; k++)
		{
			lens=ToByte((byte)6);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
			
			rpm=(short) rand.nextInt();
			lens=ToByte(rpm);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
		
			tiempo+=SLEEP_MIN;
			lens=ToByte(tiempo);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
			
			try {
				sleep(SLEEP_MIN);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			lens=ToByte((byte)8);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
			
			temperatura=(short) rand.nextInt();
			lens=ToByte(temperatura);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
			
			rpm=(short) rand.nextInt();
			lens=ToByte(rpm);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
			
			tiempo+=SLEEP_MIN;
			lens=ToByte(tiempo);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
			
			try {
				sleep(SLEEP_MIN);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return bf.array();
	}

	private void EnviaDatos1() throws IOException, InterruptedException 
	{
		byte[] newstream;
		
		while(stream_generates>0)
		{
			newstream=GenerateDataStreamP1();
			if(stream_id>1)
				EsperarACK();
			stream=newstream;
			dataOut.write(stream);
			dataOut.flush();
			
			stream_generates--;
		}
		stream_id++;
		EsperarACK();
	}

	private byte[] GenerateDataStreamP1() 
	{
		byte[] ret=new byte[1+LEN_ID+1+10+((Short.SIZE/8)*3+Integer.SIZE/8)*10];
		byte[] lens;
		ByteBuffer bf;
		int i, j=1, k;
		
		ret[0]=21;
		bf=ByteBuffer.wrap(ret);
		lens=ToByte((int)stream_id);
		for(i=0; i<lens.length; i++)
			bf.put(j+i, lens[i]);
		stream_id++;
		
		lens=ToByte((byte)10);
		j+=i;
		for(i=0; i<lens.length; i++)
			bf.put(j+i, lens[i]);
		
		for(k=0; k<10; k++)
		{
			lens=ToByte((byte)10);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
			
			rpm=(short) rand.nextInt();
			lens=ToByte(rpm);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
		
			velocidad=(short) rand.nextInt();
			lens=ToByte(velocidad);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
		
			temperatura=(short) rand.nextInt();
			lens=ToByte(temperatura);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
		
			tiempo+=SLEEP_MIN;
			lens=ToByte(tiempo);
			j+=i;
			for(i=0; i<lens.length; i++)
				bf.put(j+i, lens[i]);
			
			try {
				sleep(SLEEP_MIN);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return bf.array();
	}

	private byte[] BeginStream() 
	{
		byte[] ret=new byte[1];
		
		ret[0]=5;
		
		return ret;
	}

	private byte[] ProtocolStream() 
	{
		byte[] ret=new byte[1+LEN_ID];
		byte[] lens;
		ByteBuffer bf;
		int i, j=1;
		
		ret[0]=4;
		bf=ByteBuffer.wrap(ret);
		lens=ToByte(protocol);
		for(i=0; i<lens.length; i++)
			bf.put(j+i, lens[i]);
		
		return bf.array();
	}

	private byte[] IdStream() 
	{
		byte[] ret=new byte[1+LEN_ID*2];
		byte[] lens;
		ByteBuffer bf;
		int i, j=1;
		
		ret[0]=3;
		bf=ByteBuffer.wrap(ret);
		lens=ToByte(id);
		for(i=0; i<lens.length; i++)
			bf.put(j+i, lens[i]);
		
		lens=ToByte(APPLICATION);
		j+=i;
		for(i=0; i<lens.length; i++)
			bf.put(j+i, lens[i]);
		
		return bf.array();
	}

	private byte[] HelloStream() 
	{
		byte[] ret=new byte[1+LEN_ID*2+USER.length()*2];
		byte[] lens;
		byte[] s;
		ByteBuffer bf;
		int i=0, j=1;
		
		ret[0]=9;
		bf=ByteBuffer.wrap(ret);
		lens=ToByte(USER.length());
		for(i=0; i<lens.length; i++)
			bf.put(j+i, lens[i]);
		
		s=USER.getBytes();
		j+=i;
		for(i=0; i<s.length; i++)
			bf.put(j+i, s[i]);
		
		j+=i;
		for(i=0; i<lens.length; i++)
			bf.put(j+i, lens[i]);
		
		j+=i;
		for(i=0; i<s.length; i++)
			bf.put(j+i, s[i]);
		
		return bf.array();
	}
	
	public static void main(String[] args) 
	{
		Client c;
		
		c=new Client(Integer.toString(7));
		
		c.start();
		
		try {
			c.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
